package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;

import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;


@Service
public class SearchService {
    @Autowired
    private ElasticsearchTemplate esTemplate;
    @Autowired
    private ItemClient itemClient;

    /**
     * 把一个Spu转为一个Goods对象
     *
     * @param spu
     * @return
     */
    public Goods buildGoods(SpuDTO spu) {
        // 1 商品相关搜索信息的拼接：名称、分类、品牌、规格信息等
        // 1.1 分类 得到    ========（父分类，子分类，最小分类）
        String categoryNames = itemClient.findCategoryByIds(spu.getCategorys())
                .stream().map(CategoryDTO::getName).collect(Collectors.joining(","));
        // 1.2 品牌
        BrandDTO brand = itemClient.findBrandById(spu.getBrandId());
        // 1.3 名称等，完成拼接===================（华为P30，手机，华为）
        String all = spu.getName() + "," + categoryNames + "," + brand.getName();

        // 2 spu下的所有sku的JSON数组
        List<SkuDTO> skuList = itemClient.findSkuBySpuId(spu.getId());
        // 准备一个集合，用map来代替sku，只需要sku中的部分数据
        // ======(image:"http://image.leyou.com/images/12/15/1524297315534.jpg",title:我是手机",price:14，id":1)(.........)
        List<Map<String, Object>> skuMap = skuList.stream().map((x) -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", x.getId());
            map.put("price", x.getPrice());
            map.put("title", x.getTitle());
            //单张图片，“，”截取可以没有
            // map.put("image",x.getImages());
            map.put("image", StringUtils.substringBefore(x.getImages(), ","));
            return map;
        }).collect(Collectors.toList());
        // 3 当前spu下所有sku的价格的集合   查询用
        //=======[12,34,56]价格集合
        Set<Long> price = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 4 当前spu的规格参数
        Map<String, Object> specs = new HashMap<>();

        // 4.2 获取规格参数的值，来自于  spuDetail
        SpuDetailDTO spuDetail = itemClient.findSpuDetailById(spu.getId());
        // 4.2.1 通用规格参数值  JsonUtils.toMap(string,K.class,value.class) ============{"1":"4G","5":"不锈钢材质"}
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, Object.class);
        // 4.2.2 特有规格参数值
        // TypeReference的存在是因为java中子类可以获取到父类泛型的真实类型，
        // =============={"4":["璀璨金","星空黑"],"12":["4GB"],"13":["32GB"]}
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });

        // 4.3获取规格参数key，来自于   SpecParam   中当前分类下的需要搜索的规格
        List<SpecParamDTO> specParams = itemClient.findSpecParams(null, spu.getCid3(), true);
        for (SpecParamDTO specParam : specParams) {
            // 获取规格参数的名称
            String key = specParam.getName();
            // 获取规格参数值
            Object value = null;
            // 判断是否是通用规格
            if (specParam.getGeneric()) {
                // 通用规格    genericSpec={"1":"4G","5":"不锈钢材质"}
                value = genericSpec.get(specParam.getId());
            } else {
                // 特有规格   specialSpec={"4":["璀璨金","星空黑"],"12":["4GB"],"13":["32GB"]}
                value = specialSpec.get(specParam.getId());//list
            }
            // 判断是否是数字类型
            if (specParam.getIsNumeric()) {
                if (!org.springframework.util.StringUtils.isEmpty(specParam.getSegments())) {
                    // 是数字类型，分段     12.33
                    value = chooseSegment(value, specParam);
                }
            }
            // 添加到specs (CPU频率：2.0-2.5HG)(材质：不锈钢)
            specs.put(key, value);
        }

        Goods goods = new Goods();
        // 从spu对象中拷贝与goods对象中属性名一致的属性
        goods.setBrandId(spu.getBrandId());
        goods.setCategoryId(spu.getCid3());
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());
        goods.setCreateTime(spu.getCreateTime().getTime());
        // spu下的所有sku的JSON数组
        goods.setSkus(JsonUtils.toString(skuMap));
        // 当前spu的规格参数
        goods.setSpecs(specs);
        // 当前spu下所有sku的价格的集合
        goods.setPrice(price);
        // 商品相关搜索信息的拼接：标题、分类、品牌、规格信息等
        goods.setAll(all);
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());//2016
        String result = "其它";
        // 保存数值段   区间段  0-2000,2000-3000,3000-4000,4000-
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");//[0,2000][2000,3000]
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    public PageResult<GoodsDTO> search(SearchRequest request) {
        // 0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {//是否有东西
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        /*
        *  NativeSearchQueryBuilder方法withSourceFilter（借助FetchSourceFilter（查，不差））设置 得 属性
        * QueryBuilders方法matchQuery（查哪个，key）设置（查哪个，key）属性 后借助Operator.AND通过方法operator构建QueryBuilder
        * NativeSearchQueryBuilder方法withQuery（QueryBuilder）设置 查 属性
        * NativeSearchQueryBuilder方法withPageable(借助PageRequest.of(page, size))设置查询页码属性
        * ElasticsearchTemplate方法queryForPage（NativeSearchQueryBuilder.build,封装类.class）构建查询结果
        * 原生搜索构建器
        * FetchSourceFilter取得 资源器
        * QueryBuilder查询条件   matchQuery方法
        * */
        // 1.创建 天然搜索查询建设者
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.组织条件
        // 2.0.source过滤，控制字段数量     FetchSourceFilter取得 资源
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        //QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND);
        QueryBuilder queryBuilder1 = buildBasicQuery(request);
        // 2.1.搜索条件
        queryBuilder.withQuery(queryBuilder1);
        // 2.2.分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));

        // 3.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        // 4.1.解析分页数据
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> list = result.getContent();
        // 4.2.转换DTO
        List<GoodsDTO> dtoList = BeanHelper.copyWithCollection(list, GoodsDTO.class);

        // 5.封装并返回
        return new PageResult<>(total, totalPage, dtoList);

    }

    public Map<String, List<?>> queryFilters(SearchRequest request) {
        // 1.创建过滤项集合 有序
        Map<String, List<?>> filterList = new LinkedHashMap<>();

        // 2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.1.获取查询条件   查属性
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 2.2.减少查询结果(这里只需要聚合结果)
        // 每页显示1个手机
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 显示空的source       FetchSourceFilterBuilder  得 属性为空
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        // 3.聚合条件
        // 3.1.分类聚合       Aggregation聚合
        String categoryAgg = "categoryAgg";//聚合后列名字
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        // 3.2.品牌聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        // 4.查询并解析结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();
        // 4.1.获取分类聚合
        LongTerms cTerms = aggregations.get(categoryAgg);
        List<Long> longs = handleCategoryAgg(cTerms, filterList);//作用加k:"分类", v:DTOList  return List<long>
        // 4.2.获取分类聚合
        LongTerms bTerms = aggregations.get(brandAgg);
        handleBrandAgg(bTerms, filterList);//k:"品牌", v:DTOList
         //5
        if (longs != null && longs.size() == 1)//basicQuery查属性
            handleSpecAgg(longs.get(0),basicQuery,filterList);
            return filterList;
    }
    /*
    * cid  分类
    * basicQuery 查属性
    * */
    private void handleSpecAgg(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterList) {
        // 1.查询分类下        需要搜索过滤的规格参数名称
        List<SpecParamDTO> specParams = itemClient.findSpecParams(null, cid, true);

        // 2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.1.获取查询条件
        queryBuilder.withQuery(basicQuery);
        // 2.2.减少查询结果(这里只需要聚合结果)
        // 每页显示1个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 显示空的source      FetchSourceFilterBuilder  得属性为空
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        // 3.聚合条件
        for (SpecParamDTO param : specParams) {
            // 获取param的name，作为聚合名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        // 4.查询并获取结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();

        // 5.解析聚合结果
        for (SpecParamDTO param : specParams) {
            // 获取param的name，作为聚合名称
            String name = param.getName();
            StringTerms terms = aggregations.get(name);
            // 获取聚合结果，注意，规格聚合的结果 直接是字符串，不用做特殊处理
            List<String> paramValues = terms.getBuckets()
                    .stream()
                    .map(StringTerms.Bucket::getKeyAsString)
                    // 过滤掉空的待选项
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toList());
            // 存入map
            filterList.put(name, paramValues);
        }
    }

    private void handleBrandAgg(LongTerms terms, Map<String, List<?>> filterList) {
        // 解析bucket，得到id集合    terms品牌id的聚合
        List<Long> idList = terms.getBuckets().stream()//list<B>
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)//return long
                .collect(Collectors.toList());
        // 根据id集合查询品牌
        List<BrandDTO> brandList = itemClient.findBrandsByIds(idList);
        // 存入map
        filterList.put("品牌", brandList);
    }

    private List<Long> handleCategoryAgg(LongTerms terms, Map<String, List<?>> filterList) {
        // 解析bucket，得到id集合
        List<Long> idList = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据id集合查询分类
        List<CategoryDTO> categoryList = itemClient.findCategoryByIds(idList);
        // 存入map
        filterList.put("分类", categoryList);
        return idList;
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {
        // 构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 构建基本的match查询
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // 构建过滤条件
        Map<String, String> filters = request.getFilter();
        if(!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, String> entry : filters.entrySet()) {
                // 获取过滤条件的key
                String key = entry.getKey();
                // 规格参数的key要做前缀specs.
                if ("分类".equals(key)) {
                    key = "categoryId";
                } else if ("品牌".equals(key)) {
                    key = "brandId";
                } else {
                    key = "specs." + key;
                }
                // value
                String value = entry.getValue();
                // 添加过滤条件
                queryBuilder.filter(QueryBuilders.termQuery(key, value));
            }
        }

        return queryBuilder;
    }
    @Autowired//储存对象
    private GoodsRepository repository;
    public void createIndex(Long id) {
        // 查询spu
        SpuDTO spu = itemClient.findSpuById(id);
        // 构建成goods对象
        Goods goods = buildGoods(spu);
        // 保存数据到索引库
        repository.save(goods);
    }

    public void deleteById(Long id) {
        repository.deleteById(id);
    }
}
